iT邦幫忙

2023 iThome 鐵人賽

DAY 25
0
自我挑戰組

Rayeee 的 TypeScript 的學習日記系列 第 25

<20230926> Day 25. <TS 專案 09> 秀出彈窗吧!Showing Popup Windows!

  • 分享至 

  • xImage
  •  

昨天我們為了限制其他使用者在 index.ts 不能使用不在我們設計內的方法,所以把 Map 裡面的 Google Maps 用 Private Modifiers 設定,限定外部訪問

並且把 Map 改成class CustomMap 表示這個 class 裡面的方法都是我們客製設計的

那今天我們來加上可愛的標記彈窗吧

就是這個
https://ithelp.ithome.com.tw/upload/images/20230926/20162544S8UaPzT1zz.png


Showing Popup Windows

可以想像我們會在 class CustomMap 中多加一個方法,如

點擊標記時,上面跳個小彈窗,可以想像我們會在標記上面加個監聽器,然後在點擊時秀出該位置的彈窗

google maps 中的標記彈窗關鍵字是 infowindows

我們可以在 Maps JavaScript API 中搜尋

文件如下
https://ithelp.ithome.com.tw/upload/images/20230926/20162544KauoEoE2H2.png

可以知道 InfoWindow 裡面可以包含 contentpixelOffsetpositionmaxWidth 等屬性,我們先以簡單的 content 加入專案試試

下面是大致步驟:

  1. 在地圖標記新增一個監聽器
  2. 內容!設定 InfoWindowcontent 屬性
  3. 打開!設定 InfoWindowopen 方法
  4. 同步 class User Company 的方法

1. 在地圖標記新增一個監聽器

export class CustomMap {
  private googleMap: google.maps.Map;
  constructor() {
    this.googleMap = new google.maps.Map(
      document.getElementById("maps") as HTMLElement,
      {
        zoom: 5,
        center: {
          lat: 23,
          lng: 120.6,
        },
      }
    );
  }
  addMaker(mappable: Mappable): void {
    const markerI = new google.maps.Marker({  // 先把 Marker 做變數接起來
      map: this.googleMap,
      position: {
        lat: mappable.location.lat,
        lng: mappable.location.lng,
      },
    });

    markerI.addListener("click", () => {  // 新增 Marker 點擊時的監聽器
        // 在這邊加入標記彈窗
        infoWindow = new google.maps.InfoWindow({
          content
        });
      });
    });
  }
}

這邊我們可以繼續看官方文件也可以對 InfoWindow 點 F12,看我們程式碼內的說明書 Type definition file

這邊可以看到 InfoWindow 會接收一個型別為 google.maps.InfoWindowOptions 的參數
https://ithelp.ithome.com.tw/upload/images/20230926/201625443uWM86CE5z.png

再繼續看 google.maps.InfoWindowOptions 的定義,可以發現裡面有一個 content 參數,他的型別可以是 string|Element|Text|null

https://ithelp.ithome.com.tw/upload/images/20230926/20162544RChx9D3sG9.png

這代表著 content 可以是一段文字或是 HTML 文本,我們可以測試

export class CustomMap {
  private googleMap: google.maps.Map;
  constructor() {
    this.googleMap = new google.maps.Map(
      document.getElementById("maps") as HTMLElement,
      {
        zoom: 5,
        center: {
          lat: 23,
          lng: 120.6,
        },
      }
    );
  }
  addMaker(mappable: Mappable): void {
    const markerI = new google.maps.Marker({  // 先把 Marker 做變數接起來
      map: this.googleMap,
      position: {
        lat: mappable.location.lat,
        lng: mappable.location.lng,
      },
    });

    markerI.addListener("click", () => {  // 新增 Marker 點擊時的監聽器
        // 在這邊加入標記彈窗
        infoWindow = new google.maps.InfoWindow({
          content = `<h1>超大 H1 標籤</h1>`
        });
      });
    });
  }
}

但光是設定好內容,我們還需要加入打開標記彈窗的事件他才會出現

3. 打開!設定 InfoWindowopen 方法

一樣可以看文件或是對 InfoWindow 點 F12,看我們程式碼內的說明書 Type definition file,在裡面找我們要操作的 InfoWindow 有甚麼方法

https://ithelp.ithome.com.tw/upload/images/20230926/20162544Ae4YoNJ2tx.png

可以看到 open 方法與他接收的兩個參數 optionsanchor
options 可以是我們 class 中本身的 google.maps.Map
anchor 就是我們要打開的錨點,就是我們的 Marker 物件 markerI

export class CustomMap {
  private googleMap: google.maps.Map;
  constructor() {
    this.googleMap = new google.maps.Map(
      document.getElementById("maps") as HTMLElement,
      {
        zoom: 5,
        center: {
          lat: 23,
          lng: 120.6,
        },
      }
    );
  }
  addMaker(mappable: Mappable): void {
    const markerI = new google.maps.Marker({
      map: this.googleMap,
      position: {
        lat: mappable.location.lat,
        lng: mappable.location.lng,
      },
    });

    markerI.addListener("click", () => {
      const infoWindow = new google.maps.InfoWindow({
        content: `<h1>超大 H1 標籤</h1>`
      });
      infoWindow.open(this.googleMap, markerI);  // this way 
    });
  }
}

這樣子,位置跟打開邏輯都完成了,現在可以試試看 parcel index.html

可以看到我們設定的標籤會在點擊事件後正確出現
https://ithelp.ithome.com.tw/upload/images/20230926/201625443g3wzmbCwm.png

4. 同步 class User Company 的方法

回憶我們的需求,標記彈窗裡面的內容應該會是 User 跟 Company 裡面的值,代表 InfoWindow 應該要吃外面的參數才對,也表示 class UserCompany 裡面應該要新增屬性

那他們應該怎麼交集呢?

class UserCompany 自己本身就要有個設定標記彈窗的地方,或是設定標記彈窗要顯示參數的地方。

可以先設想這個方法 markerContent() 會設定好該 class 的彈窗自定義內容,呼叫之後就會回傳

// User.ts 
export class User {
  name: string;
  location: {
    lat: number;
    lng: number;
  };
  constructor() {
    this.name = faker.person.firstName();
    this.location = {
      lat: faker.location.latitude(),
      lng: faker.location.longitude(),
    };
  }

  markerContent(): string {
    return `user name: ${this.name}!!`;   // this way
  }
}

然後 CustomMap 那邊就要這樣接

interface Mappable {
  location: {
    lat: number;
    lng: number;
  };
}


export class CustomMap {
  private googleMap: google.maps.Map;
  constructor() {
    this.googleMap = new google.maps.Map(
      document.getElementById("maps") as HTMLElement,
      {
        zoom: 5,
        center: {
          lat: 23,
          lng: 120.6,
        },
      }
    );
  }
  addMaker(mappable: Mappable): void {
    const markerI = new google.maps.Marker({
      map: this.googleMap,
      position: {
        lat: mappable.location.lat,
        lng: mappable.location.lng,
      },
    });

    markerI.addListener("click", () => {
        content: mappable.markerContent(),    //  this way
      });
      infoWindow.open(this.googleMap, markerI);
    });
  }
}

但是這樣寫會發現,阿,報錯了

https://ithelp.ithome.com.tw/upload/images/20230926/20162544nsxSCyCKJ9.png

因為傳入 addMaker() 的參數是設定好 interface Mappable 的,現在我們想要多引用 markerContent() 方法的話,interface Mappable 也要補齊這個設定

// Map.ts
interface Mappable {
  location: {
    lat: number;
    lng: number;
  };
  markerContent(): string;    // this way
}

export class CustomMap {
  private googleMap: google.maps.Map;
  constructor() {
    this.googleMap = new google.maps.Map(
      document.getElementById("maps") as HTMLElement,
      {
        zoom: 5,
        center: {
          lat: 23,
          lng: 120.6,
        },
      }
    );
  }
  addMaker(mappable: Mappable): void {
    const markerI = new google.maps.Marker({
      map: this.googleMap,
      position: {
        lat: mappable.location.lat,
        lng: mappable.location.lng,
      },
    });

    markerI.addListener("click", () => {
      const infoWindow = new google.maps.InfoWindow({
        content: mappable.markerContent(),      // this way
      });
      infoWindow.open(this.googleMap, markerI);
    });
  }
}

這樣 TypeScript 就會檢查 index.ts 中,addMaker() 傳入的參數有沒有符合 interface Mappable 的介面設定

// index.ts
import { User } from "./User";
import { Company } from "./Company";
import { CustomMap } from "./Map";

const map = new CustomMap();
const user = new User();
const company = new Company();
map.addMaker(user);
map.addMaker(company);

像下圖因為傳入的參數 class Company 的實體裡面沒有 markerContent() 方法,所以報錯了
https://ithelp.ithome.com.tw/upload/images/20230926/20162544bPK5CA9PrR.png

所以最後,我們的 class Company 也加入 markerContent() 方法試試

// Company.ts
import { faker } from "@faker-js/faker";

export class Company {
  companyName: string;
  catchPhrase: string;
  location: {
    lat: number;
    lng: number;
  };
  constructor() {
    this.companyName = faker.company.name();
    this.catchPhrase = faker.company.catchPhrase();
    this.location = {
      lat: faker.location.latitude(),
      lng: faker.location.longitude(),
    };
  }

  markerContent(): string {
    return `
      <div>
        <h3>公司標記</h3>
        <p>We are ${this.companyName}</p>
        <p style="color: red;">We are ${this.catchPhrase}!!</p>
      </div>
    `;
  }
}

現在可以試試看 parcel index.html

可以看到我們設定的 class User Company 的標籤彈窗都會在點擊後正確出現囉

https://ithelp.ithome.com.tw/upload/images/20230926/20162544J8PCgaeCkI.png


上一篇
<20230925> Day 24. <TS 專案 08> 隱藏功能!
下一篇
<20230926> Day 26. <TS 專案 10> 來做個小優化吧
系列文
Rayeee 的 TypeScript 的學習日記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言